// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/android/network_change_notifier_delegate_android.h"

#include "base/android/context_utils.h"
#include "base/android/jni_array.h"
#include "base/logging.h"
#include "jni/NetworkChangeNotifier_jni.h"
#include "net/android/network_change_notifier_android.h"

#include "net/tencent/net_logger.h"

namespace net {

namespace {

    // Converts a Java side connection type (integer) to
    // the native side NetworkChangeNotifier::ConnectionType.
    NetworkChangeNotifier::ConnectionType ConvertConnectionType(
        jint connection_type)
    {
        switch (connection_type) {
        case NetworkChangeNotifier::CONNECTION_UNKNOWN:
        case NetworkChangeNotifier::CONNECTION_ETHERNET:
        case NetworkChangeNotifier::CONNECTION_WIFI:
        case NetworkChangeNotifier::CONNECTION_2G:
        case NetworkChangeNotifier::CONNECTION_3G:
        case NetworkChangeNotifier::CONNECTION_4G:
        case NetworkChangeNotifier::CONNECTION_NONE:
        case NetworkChangeNotifier::CONNECTION_BLUETOOTH:
            break;
        default:
            NOTREACHED() << "Unknown connection type received: " << connection_type;
            return NetworkChangeNotifier::CONNECTION_UNKNOWN;
        }
        return static_cast<NetworkChangeNotifier::ConnectionType>(connection_type);
    }

    // Converts a Java side connection type (integer) to
    // the native side NetworkChangeNotifier::ConnectionType.
    NetworkChangeNotifier::ConnectionSubtype ConvertConnectionSubtype(
        jint subtype)
    {
        DCHECK(subtype >= 0 && subtype <= NetworkChangeNotifier::SUBTYPE_LAST);

        return static_cast<NetworkChangeNotifier::ConnectionSubtype>(subtype);
    }

} // namespace

// static
void NetworkChangeNotifierDelegateAndroid::JavaIntArrayToNetworkMap(
    JNIEnv* env,
    jintArray int_array,
    NetworkMap* network_map)
{
    std::vector<int> int_list;
    base::android::JavaIntArrayToIntVector(env, int_array, &int_list);
    network_map->clear();
    for (auto i = int_list.begin(); i != int_list.end(); ++i) {
        NetworkChangeNotifier::NetworkHandle network_handle = *i;
        CHECK(++i != int_list.end());
        (*network_map)[network_handle] = static_cast<ConnectionType>(*i);
    }
}

jdouble GetMaxBandwidthForConnectionSubtype(JNIEnv* env,
    const JavaParamRef<jclass>& caller,
    jint subtype)
{
    return NetworkChangeNotifierAndroid::GetMaxBandwidthForConnectionSubtype(
        ConvertConnectionSubtype(subtype));
}

NetworkChangeNotifierDelegateAndroid::NetworkChangeNotifierDelegateAndroid()
    : observers_(new base::ObserverListThreadSafe<Observer>())
{
    JNIEnv* env = base::android::AttachCurrentThread();
    java_network_change_notifier_.Reset(
        Java_NetworkChangeNotifier_init(
            env, base::android::GetApplicationContext()));
    Java_NetworkChangeNotifier_addNativeObserver(
        env, java_network_change_notifier_.obj(),
        reinterpret_cast<intptr_t>(this));
    SetCurrentConnectionType(
        ConvertConnectionType(
            Java_NetworkChangeNotifier_getCurrentConnectionType(
                env, java_network_change_notifier_.obj())));
    SetCurrentMaxBandwidth(
        Java_NetworkChangeNotifier_getCurrentMaxBandwidthInMbps(
            env, java_network_change_notifier_.obj()));
    SetCurrentDefaultNetwork(Java_NetworkChangeNotifier_getCurrentDefaultNetId(
        env, java_network_change_notifier_.obj()));
    NetworkMap network_map;
    ScopedJavaLocalRef<jintArray> networks_and_types = Java_NetworkChangeNotifier_getCurrentNetworksAndTypes(
        env, java_network_change_notifier_.obj());
    JavaIntArrayToNetworkMap(env, networks_and_types.obj(), &network_map);
    SetCurrentNetworksAndTypes(network_map);
}

NetworkChangeNotifierDelegateAndroid::~NetworkChangeNotifierDelegateAndroid()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    observers_->AssertEmpty();
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_removeNativeObserver(
        env, java_network_change_notifier_.obj(),
        reinterpret_cast<intptr_t>(this));
}

NetworkChangeNotifier::ConnectionType
NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionType() const
{
    base::AutoLock auto_lock(connection_lock_);
    return connection_type_;
}

NetworkChangeNotifier::ConnectionSubtype
NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionSubtype() const
{
    DCHECK(thread_checker_.CalledOnValidThread());
    return ConvertConnectionSubtype(
        Java_NetworkChangeNotifier_getCurrentConnectionSubtype(
            base::android::AttachCurrentThread(),
            java_network_change_notifier_.obj()));
}

void NetworkChangeNotifierDelegateAndroid::
    GetCurrentMaxBandwidthAndConnectionType(
        double* max_bandwidth_mbps,
        ConnectionType* connection_type) const
{
    base::AutoLock auto_lock(connection_lock_);
    *connection_type = connection_type_;
    *max_bandwidth_mbps = connection_max_bandwidth_;
}

NetworkChangeNotifier::ConnectionType
NetworkChangeNotifierDelegateAndroid::GetNetworkConnectionType(
    NetworkChangeNotifier::NetworkHandle network) const
{
    base::AutoLock auto_lock(connection_lock_);
    auto network_entry = network_map_.find(network);
    if (network_entry == network_map_.end())
        return ConnectionType::CONNECTION_UNKNOWN;
    return network_entry->second;
}

NetworkChangeNotifier::NetworkHandle
NetworkChangeNotifierDelegateAndroid::GetCurrentDefaultNetwork() const
{
    base::AutoLock auto_lock(connection_lock_);
    return default_network_;
}

void NetworkChangeNotifierDelegateAndroid::GetCurrentlyConnectedNetworks(
    NetworkList* network_list) const
{
    network_list->clear();
    base::AutoLock auto_lock(connection_lock_);
    for (auto i : network_map_)
        network_list->push_back(i.first);
}

void NetworkChangeNotifierDelegateAndroid::NotifyConnectionTypeChanged(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint new_connection_type,
    jint default_netid)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    const ConnectionType actual_connection_type = ConvertConnectionType(
        new_connection_type);
    NET_LOG_BASE("NetworkChangeNotifierDelegateAndroid::NotifyConnectionTypeChanged ### type:%d", actual_connection_type);
    SetCurrentConnectionType(actual_connection_type);
    NetworkHandle default_network = default_netid;
    if (default_network != GetCurrentDefaultNetwork()) {
        SetCurrentDefaultNetwork(default_network);
        bool default_exists;
        {
            base::AutoLock auto_lock(connection_lock_);
            // |default_network| may be an invalid value (i.e. -1) in cases where
            // the device is disconnected or when run on Android versions prior to L,
            // in which case |default_exists| will correctly be false and no
            // OnNetworkMadeDefault notification will be sent.
            default_exists = network_map_.find(default_network) != network_map_.end();
        }
        // Android Lollipop had race conditions where CONNECTIVITY_ACTION intents
        // were sent out before the network was actually made the default.
        // Delay sending the OnNetworkMadeDefault notification until we are
        // actually notified that the network connected in NotifyOfNetworkConnect.
        if (default_exists) {
            observers_->Notify(FROM_HERE, &Observer::OnNetworkMadeDefault,
                default_network);
        }
    }
    observers_->Notify(FROM_HERE, &Observer::OnConnectionTypeChanged);
}

jint NetworkChangeNotifierDelegateAndroid::GetConnectionType(JNIEnv*,
    jobject) const
{
    DCHECK(thread_checker_.CalledOnValidThread());
    return GetCurrentConnectionType();
}

void NetworkChangeNotifierDelegateAndroid::NotifyMaxBandwidthChanged(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jdouble new_max_bandwidth)
{
    DCHECK(thread_checker_.CalledOnValidThread());

    NET_LOG_BASE("NetworkChangeNotifierDelegateAndroid::NotifyMaxBandwidthChanged ### new_max_bandwidth:%f", new_max_bandwidth);
    SetCurrentMaxBandwidth(new_max_bandwidth);
    observers_->Notify(FROM_HERE, &Observer::OnMaxBandwidthChanged,
        new_max_bandwidth, GetCurrentConnectionType());
}

void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkConnect(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint net_id,
    jint connection_type)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    NET_LOG_BASE("NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkConnect ### net_id:%d connection_type:%d", net_id, connection_type);
    NetworkHandle network = net_id;
    bool already_exists;
    {
        base::AutoLock auto_lock(connection_lock_);
        already_exists = network_map_.find(network) != network_map_.end();
        network_map_[network] = static_cast<ConnectionType>(connection_type);
    }
    // Android Lollipop would send many duplicate notifications.
    // This was later fixed in Android Marshmallow.
    // Deduplicate them here by avoiding sending duplicate notifications.
    if (!already_exists) {
        observers_->Notify(FROM_HERE, &Observer::OnNetworkConnected, network);
        if (network == GetCurrentDefaultNetwork()) {
            observers_->Notify(FROM_HERE, &Observer::OnNetworkMadeDefault, network);
        }
    }
}

void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkSoonToDisconnect(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint net_id)
{
    NET_LOG_BASE("NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkSoonToDisconnect ### net_id:%d", net_id);
    DCHECK(thread_checker_.CalledOnValidThread());
    NetworkHandle network = net_id;
    {
        base::AutoLock auto_lock(connection_lock_);
        if (network_map_.find(network) == network_map_.end())
            return;
    }
    observers_->Notify(FROM_HERE, &Observer::OnNetworkSoonToDisconnect, network);
}

void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkDisconnect(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint net_id)
{
    NET_LOG_BASE("NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkDisconnect ### net_id:%d", net_id);
    DCHECK(thread_checker_.CalledOnValidThread());
    NetworkHandle network = net_id;
    {
        base::AutoLock auto_lock(connection_lock_);
        if (network == default_network_)
            default_network_ = NetworkChangeNotifier::kInvalidNetworkHandle;
        if (network_map_.erase(network) == 0)
            return;
    }
    observers_->Notify(FROM_HERE, &Observer::OnNetworkDisconnected, network);
}

void NetworkChangeNotifierDelegateAndroid::NotifyPurgeActiveNetworkList(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jintArray>& active_networks)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    NetworkList active_network_list;
    base::android::JavaIntArrayToIntVector(env, active_networks,
        &active_network_list);
    NetworkList disconnected_networks;
    {
        base::AutoLock auto_lock(connection_lock_);
        for (auto i : network_map_) {
            bool found = false;
            for (auto j : active_network_list) {
                if (j == i.first) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                disconnected_networks.push_back(i.first);
            }
        }
    }
    for (auto disconnected_network : disconnected_networks)
        NotifyOfNetworkDisconnect(env, obj, disconnected_network);
}

void NetworkChangeNotifierDelegateAndroid::AddObserver(
    Observer* observer)
{
    observers_->AddObserver(observer);
}

void NetworkChangeNotifierDelegateAndroid::RemoveObserver(
    Observer* observer)
{
    observers_->RemoveObserver(observer);
}

// static
bool NetworkChangeNotifierDelegateAndroid::Register(JNIEnv* env)
{
    return RegisterNativesImpl(env);
}

void NetworkChangeNotifierDelegateAndroid::SetCurrentConnectionType(
    ConnectionType new_connection_type)
{
    base::AutoLock auto_lock(connection_lock_);
    connection_type_ = new_connection_type;
}

void NetworkChangeNotifierDelegateAndroid::SetCurrentMaxBandwidth(
    double max_bandwidth)
{
    base::AutoLock auto_lock(connection_lock_);
    connection_max_bandwidth_ = max_bandwidth;
}

void NetworkChangeNotifierDelegateAndroid::SetCurrentDefaultNetwork(
    NetworkHandle default_network)
{
    base::AutoLock auto_lock(connection_lock_);
    default_network_ = default_network;
}

void NetworkChangeNotifierDelegateAndroid::SetCurrentNetworksAndTypes(
    NetworkMap network_map)
{
    base::AutoLock auto_lock(connection_lock_);
    network_map_ = network_map;
}

void NetworkChangeNotifierDelegateAndroid::SetOnline()
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_forceConnectivityState(env, true);
}

void NetworkChangeNotifierDelegateAndroid::SetOffline()
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_forceConnectivityState(env, false);
}

void NetworkChangeNotifierDelegateAndroid::FakeNetworkConnected(
    NetworkChangeNotifier::NetworkHandle network,
    ConnectionType type)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_fakeNetworkConnected(env, network, type);
}

void NetworkChangeNotifierDelegateAndroid::FakeNetworkSoonToBeDisconnected(
    NetworkChangeNotifier::NetworkHandle network)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_fakeNetworkSoonToBeDisconnected(env, network);
}

void NetworkChangeNotifierDelegateAndroid::FakeNetworkDisconnected(
    NetworkChangeNotifier::NetworkHandle network)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_fakeNetworkDisconnected(env, network);
}

void NetworkChangeNotifierDelegateAndroid::FakePurgeActiveNetworkList(
    NetworkChangeNotifier::NetworkList networks)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_fakePurgeActiveNetworkList(
        env, base::android::ToJavaIntArray(env, networks).obj());
}

void NetworkChangeNotifierDelegateAndroid::FakeDefaultNetwork(
    NetworkChangeNotifier::NetworkHandle network,
    ConnectionType type)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_fakeDefaultNetwork(env, network, type);
}

void NetworkChangeNotifierDelegateAndroid::FakeMaxBandwidthChanged(
    double max_bandwidth_mbps)
{
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_NetworkChangeNotifier_fakeMaxBandwidthChanged(env, max_bandwidth_mbps);
}

} // namespace net
